﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace SkinnedModel
{
    #region Delegates
    public delegate void AnimationHandler(String modelID, String playerName, AnimationPlayer.PlaybackStatus status);
    #endregion

    public class AnimationInfo
    {
        public String animationName;
        public AnimationClip animationClip;
        public float scale;
        public int next;
        public int repeat;
    }

    /// <summary>
    /// Creates a container for all AnimationPlayer
    /// </summary>
    public class AnimationContainer
    {
        #region Fields
        private Dictionary<string, Containers> containerValue;
        private List<String> forRemoval;
        public Matrix modelTransform;
        private String modelID;
        private List<AnimationInfo> clipList;
        public int defClipListLength;
        private int[] animationMapping;
        #endregion

        #region Events
        public event AnimationHandler animationHandler;
        #endregion

        /// <summary>
        /// Constructs an empty Animation Container
        /// </summary>
        public AnimationContainer(String modelID)
        {
            containerValue = new Dictionary<string, Containers>();
            this.modelID = modelID;
            this.forRemoval = new List<string>();
            this.modelTransform = Matrix.Identity;
            this.clipList = new List<AnimationInfo>();
            this.animationMapping = null;
        }

        public List<AnimationInfo> ClipList
        {
            get { return clipList; }
            set { clipList = value; }
        }

        /// <summary>
        /// Gets a collection of container entries. 
        /// </summary>
        public Dictionary<string, Containers> Players
        {
            get { return containerValue; }
        }

        /// <summary>
        /// Gets main animation player
        /// </summary>
        public Containers MainPlayer
        {
            get { return containerValue["Main"]; }
        }

        /// <summary>
        /// Add a new container entry
        /// </summary>
        /// <param name="name">Name for animation player. Always use "Main" for main animation.</param>
        /// <param name="player">Animation player instance</param>
        /// <param name="clip">Animation clip to play in player</param>
        /// <param name="mode">Playback mode</param>
        /// <returns>Animation player instance</returns>
        public AnimationPlayer Add(string name, SkinningData data, String clipName, AnimationClip clip, AnimationPlayer.PlaybackMode mode)
        {
            AnimationPlayer player = new AnimationPlayer(data);
            Containers entry = new Containers(player, clipName, clip, mode);
            if (animationMapping != null) entry.ClipMap = animationMapping;
            containerValue.Add(name, entry);

            return player;
        }


        public void CreateClipMap(Dictionary<string, int> src, Dictionary<string, int> dest)
        {
            CreateClipMap(src, dest, false);
        }
        public void CreateClipMap(Dictionary<string, int> src, Dictionary<string, int> dest, int[] orgMapping, bool addToMain)
        {
            if (orgMapping == null)
                CreateClipMap(src, dest, addToMain);
            else
            {
                animationMapping = new int[orgMapping.Length];
                for (int i = 0; i < orgMapping.Length; i++)
                    animationMapping[i] = -1;
                foreach (String bone in dest.Keys)
                    animationMapping[Array.IndexOf(orgMapping, src[bone])] = dest[bone];
                if ((addToMain) && (this.MainPlayer != null))
                    this.MainPlayer.ClipMap = animationMapping;
            }
        }
        public void CreateClipMap(Dictionary<string,int> src, Dictionary<string,int> dest, bool addToMain) 
        {
            animationMapping = new int[src.Count];
            for (int i = 0; i < src.Count; i++)
                animationMapping[i] = -1;
            foreach (String bone in dest.Keys)
                animationMapping[src[bone]] = dest[bone];
            if ((addToMain) && (this.MainPlayer != null))
                this.MainPlayer.ClipMap = animationMapping;
        }
        public void AddClipMap()
        {
            if (animationMapping != null)
                this.MainPlayer.ClipMap = animationMapping;
        }
        public void AddClipMap(String player)
        {
            if (animationMapping != null)
                containerValue[player].ClipMap = animationMapping;
        }
        public void ClearClipMap()
        {
            ClearClipMap(false);
        }
        public void ClearClipMap(bool addToMain)
        {
            animationMapping = null;
            if (addToMain)
                this.MainPlayer.ClipMap = null;
        }

        /// <summary>
        /// Remove container entry
        /// </summary>
        /// <param name="name">ID of container entry to remove</param>
        /// <returns></returns>
        public bool Remove(string playerName)
        {
            //return containerValue.Remove(playerName);
            if (containerValue.ContainsKey(playerName))
            {
                forRemoval.Add(playerName);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Starts all animation in container
        /// </summary>
        public void Play()
        {
            foreach(string name in containerValue.Keys)
                if (containerValue[name].IsEnabled)
                    containerValue[name].PlayerInfo.StartClip(
                        containerValue[name].AnimationClips,
                        containerValue[name].PlaybackMode,
                        containerValue[name].Repeat,
                        containerValue[name].Start,
                        containerValue[name].End,
                        containerValue[name].CrossFade,
                        containerValue[name].FadeDuration,
                        containerValue[name].Scale,
                        containerValue[name].ClipMap);
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public void Update(GameTime gameTime)
        {
            //Update(gameTime.ElapsedGameTime, Matrix.Identity);
            Update(gameTime.ElapsedGameTime, modelTransform);

            // call delegate/callback function if necessary
            if ((animationHandler != null) && (containerValue.Count>0))
            {
                foreach (string name in containerValue.Keys)
                {
                    if (containerValue[name].PlayerInfo.CurrentStatus != containerValue[name].Status)
                    {
                        animationHandler(modelID, name, containerValue[name].PlayerInfo.CurrentStatus);
                        containerValue[name].Status = containerValue[name].PlayerInfo.CurrentStatus;
                    }

                }
            }

            // update containerValue for items to be removed
            if (forRemoval.Count > 0)
            {
                foreach (string name in forRemoval)
                    containerValue.Remove(name);
                forRemoval.Clear();
            }

        }

        /// <summary>
        /// Updates animation based on elapsed game time
        /// </summary>
        /// <param name="timeElapsed">Elapsed time</param>
        private void Update(TimeSpan timeElapsed, Matrix rootTransform)
        {
            if (containerValue.Count > 0)
            {
                // assumes main animation is always called "Main"
                foreach (Containers container in containerValue.Values)
                {
                    if (container.IsEnabled)
                    {
                        if (container.AnimationClips == null)
                        {
                            // an animation player was created but with blank clip.
                            // just set the bone transforms to the bind pose
                            container.PlayerInfo.InitBoneTransforms();
                        }
                        else
                        {
                            container.PlayerInfo.UpdateBoneTransforms(timeElapsed, true);
                        }
                    }
                }
#if false
                foreach (string name in containerValue.Keys)
                {
                    if (containerValue[name].IsEnabled)
                    {
                        if (containerValue[name].AnimationClips == null)
                        {
                            // an animation player was created but with blank clip.
                            // just set the bone transforms to the bind pose
                            containerValue[name].PlayerInfo.InitBoneTransforms();
                        }
                        else
                        {
                            containerValue[name].PlayerInfo.UpdateBoneTransforms(timeElapsed, true);
                        }
                    }
                }
#endif
                AnimationPlayer mainPlayer = containerValue["Main"].PlayerInfo;
                Matrix[] bones = new Matrix[mainPlayer.BoneTransforms.Length];
                mainPlayer.BoneTransforms.CopyTo(bones, 0);

                // Interpolate if simultaneous multiple animation
                int pass = 1;
                foreach (string name in containerValue.Keys)
                {
                    if ((name != "Main") && (containerValue[name].IsEnabled))
                    {
                        for (int i = 0; i < bones.Length; i++)
                            bones[i] = Matrix.Lerp(bones[i], containerValue[name].PlayerInfo.BoneTransforms[i], 1.0f/(float)(pass + 1));
                        pass++;
                    }
                }

                mainPlayer.UpdateProceduralTransforms(mainPlayer.LocalTransforms, bones);
                mainPlayer.UpdateWorldTransforms(rootTransform, bones);
                mainPlayer.UpdateSkinTransforms();
            }
        }
    }
}
